/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "ScriptSemantics_abstract.h"
#include "simodo/interpret/AnalyzeException.h"
#include "simodo/variable/FunctionWrapper.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/inout/format/fmt.h"

#define DEBUG

#ifdef DEBUG
    #include "simodo/interpret/Interpret.h"
#endif

#include <functional>
#include <iterator>
#include <cassert>

#if __cplusplus >= __cpp_2017
#include <filesystem>
namespace fs = std::filesystem;
#else
#include <experimental/filesystem>
namespace fs = std::filesystem::experimental;
#endif

namespace simodo::interpret
{
    InterpretState ScriptSemantics_abstract::executeNone()
    {
        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executePushConstant(const ast::Node & op)
    {
        variable::Variable   constant_variable;
        [[maybe_unused]] size_t index = 0;

        switch(op.token().type())
        {
        case inout::LexemeType::Annotation:
            constant_variable = { u"", op.token().lexeme(), op.token().location() };
            break;
        case inout::LexemeType::Number:
            if (op.token().lexeme().substr(0,2) == u"0b") 
                constant_variable = { u"", 
                        static_cast<int64_t>(std::stoll(inout::toU8(op.token().lexeme().substr(2)),nullptr,2)), 
                        op.token().location() };
            else if (op.token().lexeme().substr(0,2) == u"0o") 
                constant_variable = { u"", 
                        static_cast<int64_t>(std::stoll(inout::toU8(op.token().lexeme().substr(2)),nullptr,8)), 
                        op.token().location() };
            else if (op.token().lexeme().substr(0,2) == u"0x") 
                constant_variable = { u"", 
                        static_cast<int64_t>(std::stoll(inout::toU8(op.token().lexeme().substr(2)),nullptr,16)), 
                        op.token().location() };
            else if (op.token().qualification() == inout::TokenQualification::Integer)
                constant_variable = { u"", static_cast<int64_t>(std::stoll(inout::toU8(op.token().lexeme()))), op.token().location() };
            else
                constant_variable = { u"", std::stod(inout::toU8(op.token().lexeme())), op.token().location() };
            break;
        case inout::LexemeType::Punctuation:
            if (op.token().lexeme() == u"true" || op.token().lexeme() == u"false")
                constant_variable = { u"", op.token().lexeme() == u"true", op.token().location() };
            else if (op.token().lexeme() == u"null") {
                constant_variable = variable::null_variable(op.token().location());
                index = constant_variable.value().variant().index();
            }
            break;
        default:
            throw bormental::DrBormental("ScriptSemantics_abstract::executePushConstant", 
                                        inout::fmt("Invalid internal type of constant to convert (%1)")
                                            .arg(getLexemeTypeName(op.token().type())));
        }

        if (!op.branches().empty()) {
            std::vector<variable::Value> unit_parts;

            for(const ast::Node & n : op.branches())
                unit_parts.push_back(n.token().lexeme());

            constant_variable.spec().set(u"unit", unit_parts);
        }

        stack().push(constant_variable);

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executePushVariable(const inout::Token & variable_name)
    {
#ifdef DEBUG
        [[maybe_unused]] Interpret * p_inter = static_cast<Interpret *>(&inter());
#endif
        name_index_t index = stack().find(variable_name.lexeme());

        if (index != UNDEFINED_NAME_INDEX) {
            variable::Variable ref = { {}, stack().variable(index).makeReference(), variable_name.location() };

            stack().push(ref);
            notifyNameUsed(ref.origin(), variable_name.location());

            return InterpretState::Flow;
        }

        std::shared_ptr<variable::Object> self_object = inter().expr().self();

        if (self_object) {
            const variable::Variable & var = self_object->getVariableByName(variable_name.token());

            if (!var.name().empty()) {
                variable::Variable ref = { {}, var.makeReference(), variable_name.location() };

                stack().push(ref);
                notifyNameUsed(ref.origin(), variable_name.location());
                return InterpretState::Flow;
            }
        }

        if (variable_name.lexeme() == SELF_VARIABLE_NAME) {
            if (!self_object)
                self_object = inter().expr().makeSelf();

            if (self_object) {
                variable::Variable self = {{}, self_object, variable_name.location(), {{{variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_MODULE}}}};

                stack().push(self);
                notifyDeclared(self);
                return InterpretState::Flow;
            }
        }

        stack().push({variable_name.lexeme(), {}, variable_name.location(), {}}); 
        throw AnalyzeException("ScriptSemantics_abstract::executePushVariable", 
                                variable_name.location().makeLocation(inter().files()),
                                inout::fmt("Variable '%1' not found")
                                    .arg(variable_name.lexeme()));
    }

    InterpretState ScriptSemantics_abstract::executeObjectElement(const ast::Node & op)
    try
    {
#ifdef DEBUG
        [[maybe_unused]] Interpret * p_inter = static_cast<Interpret *>(&inter());
#endif
        variable::Variable & candid = stack().variable(stack().top());
        variable::Variable & origin = candid.origin();

        if (origin.value().isNull()) {
            stack().pop();
            stack().push(variable::error_variable(op.token().location(),op.token().lexeme()));
            return InterpretState::Flow;
        }

        variable::Variable obj_variable = origin;

        if (op.token().lexeme() == u"spec") {
            std::shared_ptr<variable::Object> object  = obj_variable.spec().object();
            variable::Variable                element {op.token().lexeme(), object, op.token().location()};
            stack().pop();
            stack().push(element);
            notifyNameUsed(element, op.token().location());
            return InterpretState::Flow;
        }

        if (!obj_variable.value().isObject())
            obj_variable = inter().expr().convertVariable(candid, variable::ValueType::Object);

        assert(obj_variable.value().isObject());

        std::shared_ptr<variable::Object> object  = obj_variable.value().getObject();
        variable::Variable &              element = object->getVariableByName_mutable(op.token().lexeme());

        if (!element.name().empty() && !hidden(element)) {
            variable::VariableSet_t ref_spec;
            if (element.type() == variable::ValueType::Function) {
                setParentObjectForFunction(element, object);
                if (object->flags().isSet(variable::ObjectFlag::Fiber)
                 && _fiber_flow_mode == FiberFlowMode::InDemand) {
                    ref_spec.push_back({variable::SPEC_FLOW, object});
                    _fiber_flow_mode = FiberFlowMode::Prepared;
                }
            }

            stack().pop();
            stack().pushRef(element, op.token().location(), ref_spec);
            notifyNameUsed(element, op.token().location());

            return InterpretState::Flow;
        }

        const ast::Node * p_next_node = inter().lookupOperationNode(+1);

        if (p_next_node
         && checkSemanticName(p_next_node->host()) 
         && ScriptOperationCode(p_next_node->operation()) == ScriptOperationCode::Assignment) {
            variable::Variable & setter = object->getVariableByName_mutable(u"set_" + op.token().lexeme());

            if (!setter.name().empty() && !hidden(setter) && setter.type() == variable::ValueType::Function) {
                setParentObjectForFunction(setter, object);
                stack().pop();
                stack().pushRef(setter, op.token().location());
                notifyNameUsed(setter, op.token().location());
                _setter_in_stack = true;
                return InterpretState::Flow;
            }
        }
        else {
            variable::Variable & getter = object->getVariableByName_mutable(u"get_" + op.token().lexeme());

            if (!getter.name().empty() && !hidden(getter) && getter.type() == variable::ValueType::Function) {
                setParentObjectForFunction(getter, object);
                stack().pop();
                stack().pushRef(getter, op.token().location());
                notifyNameUsed(getter, op.token().location());
                return executeFunctionCall({});
            }
        }

        if (hidden(element))
            throw AnalyzeException("ScriptSemantics_abstract::executeObjectElement", 
                                    op.token().location().makeLocation(inter().files()),
                                    inout::fmt("Property '%1' is hidden in '%2' and have not getter 'get_%3'")
                                        .arg(op.token().lexeme())
                                        .arg(obj_variable.name())
                                        .arg(op.token().lexeme()));

        throw AnalyzeException("ScriptSemantics_abstract::executeObjectElement", 
                                op.token().location().makeLocation(inter().files()),
                                inout::fmt("Property '%1' not found in '%2'")
                                    .arg(op.token().lexeme())
                                    .arg(obj_variable.name()));

    }
    catch (...)
    {
        stack().pop();
        stack().push(variable::error_variable());
        throw;
    }

    InterpretState ScriptSemantics_abstract::executeFunctionCall(const ast::Node & op)
    {
#ifdef DEBUG
        [[maybe_unused]] Interpret * p_inter = static_cast<Interpret *>(&inter());
#endif
        name_index_t       function_index  { stack().top() };
        variable::Variable function        { stack().variable(function_index) };

        if (function.origin().type() != variable::ValueType::Function) {
            if (function.origin().type() == variable::ValueType::Object) {
                std::shared_ptr<variable::Object> object    = function.origin().value().getObject();
                const variable::Variable          init_func = object->getVariableByName(u"__init__");

                if (!init_func.name().empty()) {
                    if (init_func.type() == variable::ValueType::Function) {
                        std::shared_ptr<variable::Object> function_object  = init_func.value().getObject();

                        if (function_object->variables().size() > 1
                         && function_object->variables()[0].type() == variable::ValueType::IntFunction)
                            function_object->variables()[0].value().getIntFunctionParent() = object;
                    }
                    function = init_func;
                }
            }

            if (function.origin().type() != variable::ValueType::Function)
                function = inter().expr().convertVariable(function, variable::ValueType::Function);

            stack().pop();
            stack().push(function);
        }

        boundary_index_t   boundary_index  { stack().startBoundary(BoundScope::Local, BOUNDARY_MARK_FUNCTION_FRAME) };

        // Вычисляем параметры функции, а в конце вызываем функцию
        inter().addFlowLayer(
                op, 
                [this, boundary_index, function](const FlowLayerInfo & flow) 
                {
                    if (flow.error_sign) {
                        stack().clearBoundary(boundary_index);
                        stack().pop();
                        stack().push(variable::error_variable()); 
                        return;
                    }

                    bool                  invoke      = true;
                    Interpret_interface * p_interpret = nullptr;

                    if (_fiber_flow_mode == FiberFlowMode::Prepared) {
                        if (checkInterpretType(InterpretType::Preview)) {
                            const variable::Variable & flow_variable = function.spec().object()->getVariableByName(variable::SPEC_FLOW);
                            assert(!flow_variable.name().empty());
                            const ChildFlowInfo * info = inter().parallelize().findChildFlowInfo(flow_variable);
                            assert(info);
                            p_interpret = info->child;
                        }
                        else {
                            p_interpret = _interpret;
                            invoke = false;
                        }

                        _fiber_flow_mode = FiberFlowMode::Launched;
                        notifyRemoteFunctionLaunch(function.location());
                    }
                    else
                        p_interpret = _interpret;

                    if (invoke && checkInterpretType(InterpretType::Analyzer)) {
                        std::shared_ptr<variable::Object> our_function_object = function.origin().value().getFunction();
                        auto it = std::find_if( _functions_for_analyze.begin(), _functions_for_analyze.end(),
                                [our_function_object](const variable::Variable & f){
                                    std::shared_ptr<variable::Object> f_object = f.origin().value().getFunction();
                                    return f_object.get() == our_function_object.get();
                                });

                        if (it != _functions_for_analyze.end())
                            _functions_for_analyze.erase(it);
                        else if (stack().isRecursiveCalls()) {
                            const auto [begin,end] = stack().boundaryLowAndTop(boundary_index);
                            variable::Variable & function_origin = stack().variable(begin-1).origin();
                            assert(function_origin.value().isFunction());
                            function_origin.origin().spec().set(variable::SPEC_RECURSIVE, true);
                            invoke = false;
                        }
                    }

                    inter().expr().callPreparedFunction(
                            p_interpret,
                            boundary_index,
                            notification(),
                            invoke);
                });

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeProcedureCheck()
    {
        /// \todo Проверять, что была вызвана не функция, а процедура?

        stack().pop();
        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executePrint(bool need_detailed_report)
    {
        inter().expr().print(need_detailed_report);
        stack().pop();

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeBlock(bool is_beginning_of_block, const ast::Node & op)
    {
        if (is_beginning_of_block) {
            boundary_index_t boundary_index = stack().startBoundary(BoundScope::Local);

            inter().addFlowLayer(
                    op,
                    [this, boundary_index](const FlowLayerInfo & ) 
                    {
                        stack().clearBoundary(boundary_index);
                    });
        }

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executePop()
    {
        stack().pop();

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeFunctionDefinition( 
                                            const ast::Node * pop_closure, 
                                            const ast::Node * pop_params,
                                            const ast::Node * pop_return_type,
                                            const ast::Node & op_body)
    {
        boundary_index_t boundary_index = stack().startBoundary();

        std::function<void(const FlowLayerInfo &)> call_back = 
                [this, boundary_index](const FlowLayerInfo & flow)
                {
                    if (flow.error_sign) {
                        _function_definition_data.setReturnType({});
                        stack().clearBoundary(boundary_index);
                        _function_definition_mode = false;
                        stack().push(variable::error_variable());
                        return;
                    }

                    if (_function_definition_data.state() == FunctionDefinition_data::State::Params) {
                        auto [begin_index,end_index] = stack().boundaryLowAndTop(boundary_index);
                        assert(end_index-begin_index > 0);

                        variable::Object params;

                        for(name_index_t i=begin_index; i < end_index; ++i) {
                            const variable::Variable & param   = stack().variable(i);
                            const variable::Variable & closure = _function_definition_data.closures().getVariableByName(param.name());
                            if (!closure.name().empty()) {
                                inout::TokenLocation location = param.location();

                                _function_definition_data.setReturnType({});
                                stack().clearBoundary(boundary_index);
                                _function_definition_mode = false;
                                stack().push(variable::error_variable());

                                throw AnalyzeException("ScriptSemantics_abstract::executeFunctionDefinition", 
                                                        location.makeLocation(inter().files()), 
                                                        inout::fmt("The argument and closure have the same names"));
                            }
                            params.variables().push_back(param);
                        }

                        stack().pop(end_index - begin_index);

                        _function_definition_data.setParams(params);

                        if (_function_definition_data.pop_return_type()) {
                            inter().addFlowLayer(*_function_definition_data.pop_return_type(), flow.on_layer_end);
                            return;
                        }

                        _function_definition_data.setReturnType({});
                        stack().clearBoundary(boundary_index);
                        _function_definition_mode = false;
                        stack().push(_function_definition_data.createFunction());
                        return;
                    }

                    assert(_function_definition_data.state() == FunctionDefinition_data::State::Return_Type);

                    _function_definition_data.setReturnType(
                                createVariable(stack().accumulator(), flow.code.token(), variable::SPEC_ORIGIN_TYPE));
                    stack().clearBoundary(boundary_index);
                    _function_definition_mode = false;
                    stack().push(_function_definition_data.createFunction());
                    return;
                };

        variable::Object closures;

        if (pop_closure)
            for(const ast::Node & n : pop_closure->branches()) 
                try
                {
                    variable::Variable v;

                    if (n.token().lexeme() == u"*")
                        ;
                    else if (n.token().lexeme() == SELF_VARIABLE_NAME)
                        v = {n.token().lexeme(), {}, n.token().location()};
                    else {
                        // Проверка, что имя замыкания указано правильно
                        // (в PostAssignment реально уместное определение замыканий с учётом доопределения 
                        // переменных в инициализации)
                        name_index_t i = stack().find(n.token().lexeme());

                        if (i == UNDEFINED_NAME_INDEX)
                            throw AnalyzeException("ScriptSemantics_abstract::executeFunctionDefinition", 
                                                    n.token().location().makeLocation(inter().files()),
                                                    inout::fmt("Variable '%1' not found")
                                                        .arg(n.token().lexeme()));

                        v = stack().variable(i).origin().copyVariable();
                    }
                    v.setLocation(n.token().location());
                    closures.variables().push_back(v);
                }
                catch(...)
                {
                    stack().clearBoundary(boundary_index);
                    _function_definition_mode = false;
                    stack().push(variable::error_variable());
                    throw;
                }

        _function_definition_data.initiate(closures, pop_return_type, &op_body);
        _function_definition_mode = true;

        if (pop_params) {
            inter().addFlowLayer(*pop_params, call_back);
            return InterpretState::Flow;
        }

        variable::Object params;
        _function_definition_data.setParams(params);

        if (pop_return_type) {
            inter().addFlowLayer(*pop_return_type, call_back);
            return InterpretState::Flow;
        }

        _function_definition_data.setReturnType({});
        stack().clearBoundary(boundary_index);
        _function_definition_mode = false;
        stack().push(_function_definition_data.createFunction());

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeFunctionDefinitionEnd(const ast::Node & )
    {
        name_index_t         func_index    = stack().top();
        variable::Variable & func_variable = stack().variable(func_index).origin();
        assert(func_variable.value().isFunction());
        _functions_for_closures.push_back(func_variable.value().getFunction());

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executePostAssignment(const ast::Node & )
    {
        while(!_functions_for_closures.empty()) {
            std::shared_ptr<variable::Object> function_object = _functions_for_closures.back();
            _functions_for_closures.pop_back();

            assert(function_object->variables().size() >= 2);

            variable::InternalFunction & 
                             internal_function = std::get<variable::InternalFunction>(function_object->variables()[0].value().variant());

            variable::Object closures;

            for(const variable::Variable & closure : internal_function.closures.variables())
                if (closure.name().empty()) {
                    boundary_index_t module_bound = stack().findNearestMarkedBoundary(BOUNDARY_MARK_MODULE_FRAME, BoundScope::Global);
                    if (module_bound != UNDEFINED_BOUNDARY_INDEX) {
                        auto [begin_index, end_index] = stack().boundaryLowAndTop(module_bound);
                        for(name_index_t i = begin_index; i < end_index; ++i) {
                            variable::Variable & v = stack().variable(i);
                            closures.variables().push_back(v.copyVariable());
                        }
                    }
                }
                else {
                    name_index_t origin_index = stack().find(closure.name());
                    if (origin_index != UNDEFINED_NAME_INDEX) {
                        const variable::Variable & origin = stack().variable(origin_index).origin();
                        closures.variables().push_back(origin.copyVariable());
                        notifyNameUsed(origin,closure.location());
                    }
                    else
                        closures.variables().push_back(closure);
                }

            internal_function.closures.variables().swap(closures.variables());
        }

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeFunctionTethered(const ast::Node & )
    {
        name_index_t         func_index    = stack().top();
        variable::Variable & func_variable = stack().variable(func_index).origin();
        assert(func_variable.value().isFunction());

        func_variable.spec().set(variable::SPEC_TETHERED, true);

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeReturn(const ast::Node & op)
    {
        ScriptOperationCode operation_code    = static_cast<ScriptOperationCode>(op.operation());
        boundary_index_t    function_boundary = stack().findNearestMarkedBoundary(BOUNDARY_MARK_FUNCTION_FRAME);

        if (operation_code == ScriptOperationCode::ReturnExpression) {
            inter().expr().setReturnValue(stack().variable(stack().top()).origin().value());
            stack().pop();
        }

        if (function_boundary == UNDEFINED_BOUNDARY_INDEX)
            throw AnalyzeException( "ScriptSemantics_abstract::executeReturn", 
                                    op.token().makeLocation(inter().files()), 
                                    inout::fmt("Inappropriate use of the return statement"));

        const auto [begin,end]      = stack().boundaryLowAndTop(function_boundary);

        name_index_t                function_index       { begin-1 };
        variable::Variable &        function             { stack().variable(function_index).origin() };
        variable::FunctionWrapper   func                 { function };
        variable::ValueType         return_declared_type { func.getReturnDeclarationVariable().type() };

        if (return_declared_type != variable::ValueType::Null && inter().expr().return_value().type() != variable::ValueType::Null
         && return_declared_type != inter().expr().return_value().type())
            inter().expr().setReturnValue(inter().expr().convertVariable({u"", inter().expr().return_value(), op.token().location()},return_declared_type).value());

        if (return_declared_type != variable::ValueType::Null && operation_code == ScriptOperationCode::Return)
            throw AnalyzeException( "ScriptSemantics_abstract::executeReturn", 
                                    op.token().makeLocation(inter().files()), 
                                    inout::fmt("The function must return a value"));

        if (checkInterpretType(InterpretType::Preview)
         || function.name().substr(0,2) == u"__"
        ) {
            while(!_cycle_data_stack.empty() && _cycle_data_stack.back().boundary_index > function_boundary)
                _cycle_data_stack.pop_back();

            const ast::Node * code = std::get<variable::InternalFunction>(func.getCallingAddressVariable().variant()).code;
            bool returned = inter().flushLayers(*code, true);
            assert(returned);
        }

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeFor(const inout::Token & ,
                                                      const ast::Node * pop_variable, 
                                                      const ast::Node * pop_source, 
                                                      const ast::Node * pop_body)
    {
        assert(pop_variable);
        assert(pop_source);
        assert(pop_body);

        boundary_index_t boundary_index = stack().startBoundary(BoundScope::Local, BOUNDARY_MARK_CYCLE_FRAME);

        _cycle_data_stack.push_back({boundary_index, pop_variable, pop_source, pop_body});

        inter().addFlowLayer(
                *_cycle_data_stack.back().pop_variable, 
                [this, boundary_index](const FlowLayerInfo & flow)
                {
                    if (flow.error_sign) {
                        stack().clearBoundary(boundary_index);
                        _cycle_data_stack.pop_back();
                        return;
                    }

                    _cycle_data_stack.back().variable_index = stack().top();
                    _cycle_data_stack.back().typed_variable = stack().variable(_cycle_data_stack.back().variable_index).origin();

                    inter().addFlowLayer(*_cycle_data_stack.back().pop_source, _for_source_callback);
                });

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeWhile(const inout::Token & ,
                                                        const ast::Node * pop_expression, 
                                                        const ast::Node * pop_body)
    {
        assert(pop_expression);
        assert(pop_body);

        boundary_index_t boundary_index = stack().startBoundary(BoundScope::Local, BOUNDARY_MARK_CYCLE_FRAME);

        _cycle_data_stack.push_back({boundary_index, pop_expression, pop_body});

        inter().addFlowLayer(*_cycle_data_stack.back().pop_expression, _cycle_condition_callback);

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeDoWhile(const inout::Token & ,
                                                          const ast::Node * pop_body, 
                                                          const ast::Node * pop_expression)
    {
        assert(pop_body);
        assert(pop_expression);

        boundary_index_t boundary_index = stack().startBoundary(BoundScope::Local, BOUNDARY_MARK_CYCLE_FRAME);

        _cycle_data_stack.push_back({boundary_index, pop_expression, pop_body});

        inter().addFlowLayer(*_cycle_data_stack.back().pop_body, _cycle_body_callback);

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeBreak(const ast::Node & op)
    {
        if (_cycle_data_stack.empty()
         || UNDEFINED_BOUNDARY_INDEX == stack().findNearestMarkedBoundary(BOUNDARY_MARK_CYCLE_FRAME))
            throw AnalyzeException( "ScriptSemantics_abstract::executeReturn",    
                                    op.token().makeLocation(inter().files()), 
                                    inout::fmt("Inappropriate use of the 'break' statement"));

        if (checkInterpretType(InterpretType::Preview)) {
            inter().flushLayers(*_cycle_data_stack.back().pop_body,false);
            _cycle_data_stack.pop_back();
        }

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeContinue(const ast::Node & op)
    {
        if (_cycle_data_stack.empty()
         || UNDEFINED_BOUNDARY_INDEX == stack().findNearestMarkedBoundary(BOUNDARY_MARK_CYCLE_FRAME))
            throw AnalyzeException( "ScriptSemantics_abstract::executeReturn", 
                                    op.token().makeLocation(inter().files()), 
                                    inout::fmt("Inappropriate use of the 'continue' statement"));

        if (checkInterpretType(InterpretType::Preview))
            inter().flushLayers(*_cycle_data_stack.back().pop_body,true);

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeReference(const ast::Node & )
    {
        name_index_t         var_index  = stack().top();
        variable::Variable & var        = stack().variable(var_index);

        if (!var.value().isRef())
            throw AnalyzeException("ScriptSemantics_abstract::executeArrayElement", 
                                    var.location().makeLocation(inter().files()), 
                                    inout::fmt("It is unacceptable to take a reference for a temporary value"));

        var.spec().set(variable::SPEC_REFERENCE, true);

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeArrayElement(const ast::Node & op)
    {
        name_index_t       array_index    = stack().top();
        variable::Variable array_variable = stack().variable(array_index);

        if (array_variable.origin().type() != variable::ValueType::Array)
            array_variable = inter().expr().convertVariable(array_variable, variable::ValueType::Array);

        boundary_index_t boundary_index = stack().startBoundary();

        inter().addFlowLayer(
                op, 
                [this, array_variable, boundary_index](const FlowLayerInfo & flow) 
                {
                    if (flow.error_sign) {
                        stack().clearBoundary(boundary_index);
                        stack().pop();
                        stack().push(variable::error_variable(flow.code.bound().location()));
                        return;
                    }

                    variable::VariableRef v;

                    try
                    {
                        std::vector<variable::index_t> indexes;
                        auto [begin_index, end_index] = stack().boundaryLowAndTop(boundary_index);

                        for(name_index_t i = begin_index; i < end_index; ++i) {
                            const variable::Variable & index_variable = stack().variable(i);
                            if (index_variable.origin().type() == variable::ValueType::Int) {
                                int64_t index = index_variable.origin().value().getInt();

                                if (index >= 0 && index < std::numeric_limits<variable::index_t>::max()) {
                                    indexes.push_back(variable::index_t(index));
                                    continue;
                                }
                            }

                            throw AnalyzeException("ScriptSemantics_abstract::executeArrayElement", 
                                    stack().variable(i).location().makeLocation(inter().files()), 
                                    inout::fmt("The index must be a positive integer number and not exceed ")
                                        .arg(std::numeric_limits<variable::index_t>::max()));
                        }

                        v = array_variable.origin().value().getArray()->getRefByIndex(indexes);
                    }
                    catch(...)
                    {
                        stack().clearBoundary(boundary_index);
                        stack().pop();
                        stack().push(variable::error_variable(flow.code.bound().location()));
                        throw;
                    }

                    stack().clearBoundary(boundary_index);
                    stack().pop();
                    stack().push({{}, v, flow.code.bound().location()});
                });

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeObjectStructure(const ast::Node & op)
    {
        if (op.branches().empty()) {
            stack().push({ {}, variable::VariableSet_t {}, op.token().location(), 
                         { {{variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_STRUCTURE}, }} });
            return InterpretState::Flow;
        }

        _executeRecordStructure_stack.push_back({stack().startBoundary(), op});

        inter().addFlowLayer(
                op.branches().front(),                 
                [this](const FlowLayerInfo & flow) 
                {
                    boundary_index_t  boundary_index    = _executeRecordStructure_stack.back().boundary_index;
                    const ast::Node & op                = _executeRecordStructure_stack.back().op;
                    size_t &          op_branches_index = _executeRecordStructure_stack.back().op_branches_index;

                    try
                    {
                        // Проверяем уникальность имени
                        auto [begin_index, end_index] = stack().boundaryLowAndTop(boundary_index);
                        for(name_index_t i=begin_index; i < end_index-1; ++i)
                            if (stack().variable(i).name() == flow.code.token().lexeme())
                                throw AnalyzeException("ScriptSemantics_abstract::executeObjectStructure", 
                                                flow.code.token().makeLocation(inter().files()),
                                                inout::fmt("Duplicate object element name '%1'")
                                                    .arg(flow.code.token().lexeme()));

                        variable::Variable         param             = { flow.code.token().lexeme(), {}, flow.code.token().location() };
                        name_index_t               param_value_index = stack().top();
                        const variable::Variable & param_value       = stack().variable(param_value_index);

                        inter().expr().assignVariable(param, param_value);
                        stack().pop();
                        stack().push(param);

                        notifyDeclared(param);

                        // Переключаемся на следующий элемент объекта
                        op_branches_index += 1;

                        if (op_branches_index >= op.branches().size()) {
                            // Элементы закончились
                            std::shared_ptr<variable::Object> obj = stack().convertToObject(boundary_index);
                            stack().clearBoundary(boundary_index);
                            stack().push({ {}, obj, op.token().location(), 
                                         { {{variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_STRUCTURE}, }} });
                            _executeRecordStructure_stack.pop_back();
                        }
                        else
                            // Ставим на обработку выражение справа от очередной переменной объекта
                            inter().addFlowLayer(op.branches()[op_branches_index], flow.on_layer_end);
                    }
                    catch(...)
                    {
                        if (stack().clearBoundary(boundary_index))
                            stack().push(variable::error_variable());
                        _executeRecordStructure_stack.pop_back();
                        throw;
                    }
                });

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeArrayStructure(const ast::Node & op)
    {
        boundary_index_t boundary_index = stack().startBoundary();

        inter().addFlowLayer(
                op, 
                [this, boundary_index](const FlowLayerInfo & flow) 
                {
                    std::vector<variable::Value> values;

                    try
                    {
                        auto [begin_index,end_index] = stack().boundaryLowAndTop(boundary_index);
                        for(name_index_t i=begin_index; i < end_index; ++i)
                            values.push_back(stack().variable(i).origin().value().copy());
                    }
                    catch(...)
                    {
                        stack().clearBoundary(boundary_index);
                        stack().push(variable::error_variable());
                        throw;
                    }

                    stack().clearBoundary(boundary_index);
                    std::shared_ptr<variable::Array> array = std::make_shared<variable::Array>(
                                                                variable::ValueType::Null, 
                                                                std::vector<variable::index_t> {static_cast<variable::index_t>(values.size())}, 
                                                                values);

                    stack().push({{}, array, flow.code.token().location(), {}});
                });

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeImport(const inout::Token & path, const ast::Node & params)
    try
    {
        if (!params.branches().empty())
            throw AnalyzeException("ScriptSemantics_abstract::executeImport", 
                            params.branches().front().token().makeLocation(inter().files()),
                            inout::fmt("Module parameters are not supported"));

        std::string module_path = makeModulePath(path, inter().files().at(path.location().uri_index()));

        if (path.type() == inout::LexemeType::Annotation && !_module_management.isTheModuleLoaded(module_path)) {
            if (!fs::exists(module_path))
                throw AnalyzeException("ScriptSemantics_abstract::executeImport", 
                                path.makeLocation(inter().files()),
                                inout::fmt("File '%1' was not found")
                                    .arg(module_path));

            notifyRef(path, inout::toU16(module_path));
            inter().addFile(module_path);

            const ast::Node * node = _module_management.getCode(module_path,inter().files());
            if (node == nullptr)
                throw interpret::AnalyzeException("ScriptSemantics_abstract::executeImport", 
                                            path.makeLocation(inter().files()),
                                            _module_management.last_error());

            boundary_index_t boundary_index = stack().startBoundary(BoundScope::Global, BOUNDARY_MARK_MODULE_FRAME);

            inter().addFlowLayer(
                    *node, 
                    [this,boundary_index,path,module_path](const FlowLayerInfo &)
                    {
                        std::shared_ptr<variable::Object> record;

                        try
                        {
                            std::shared_ptr<variable::Object> module_object = stack().convertToObject(boundary_index);
                            if (module_object) {
                                std::shared_ptr<variable::Module_interface> module 
                                                = _module_management.registerSoftModule(module_path,module_object);
                                if (module)
                                    /// \todo Пересмотреть странную передачу самого себя в свой же метод.
                                    /// PVS Studio: warn V678 An object is used as an argument to its own method.
                                    /// Consider checking the first actual argument of the 'instantiate' function.
                                    record = std::make_shared<variable::Object>(module->instantiate(module));
                            }

                            if (!record)
                                throw AnalyzeException("ScriptSemantics_abstract::executeImport", 
                                                path.makeLocation(inter().files()),
                                                inout::fmt("Unable to load module '%1'")
                                                    .arg(module_path));
                        }
                        catch(...)
                        {
                            stack().clearBoundary(boundary_index);
                            stack().push({ {}, {}, path.location(), 
                                        {{{variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_MODULE}, }} });
                            throw;
                        }

                        stack().clearBoundary(boundary_index);
                        stack().push({ {}, record, path.location(), 
                                    {{{variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_MODULE}, }} });
                    });
        }
        else {
            std::shared_ptr<variable::Object> object = _module_management.produceObject(module_path,&inter());
            if (!object) {
                std::string error_text = _module_management.last_error();
                if (error_text.empty())
                    error_text = inout::fmt("Unable to load module '%1'").arg(module_path);
                    
                throw interpret::AnalyzeException("ScriptSemantics_abstract::executeImport", 
                                            path.makeLocation(inter().files()),
                                            error_text);
            }

            stack().push({ {}, object, path.location(), 
                            {{{variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_MODULE}, }} });
        }

        return InterpretState::Flow;
    }
    catch(...)
    {
        stack().push({ {}, variable::VariableSet_t {}, path.location(), 
                    {{{variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_MODULE}, }} });
        throw;
    }

    InterpretState ScriptSemantics_abstract::executeAnnouncement()
    {
        name_index_t               top             = stack().top();
        const variable::Variable & contract        = stack().variable(top);

        appendContractProperties(contract, stack().accumulator());
        stack().pop();
        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeContract(const ast::Node & op)
    {
        if (stack().find(op.token().lexeme(),interpret::BoundScope::Local) != UNDEFINED_NAME_INDEX) {
            stack().push(variable::error_variable());
            throw AnalyzeException( "ScriptSemantics_abstract::executeContract", 
                                    op.token().location().makeLocation(inter().files()), 
                                    inout::fmt("The name '%1' has already been declared in the local scope")
                                        .arg(op.token().lexeme()));
        }

        variable::Variable new_contract {op.token().lexeme(), {}, op.token().location()};

        appendContractProperties(stack().accumulator(), new_contract);

        if (new_contract.type() == variable::ValueType::Null
         && new_contract.spec().object()->getVariableByName(SPEC_NAME_INITIAL_VALUE).name().empty()
         && new_contract.spec().object()->getVariableByName(SPEC_NAME_BUILT_IN_TYPE).name().empty()) {
            if (ScriptOperationCode(op.operation()) == ScriptOperationCode::Type)
                throw AnalyzeException( "ScriptSemantics_abstract::executeContract", 
                                        op.token().location().makeLocation(inter().files()), 
                                        inout::fmt("Type '%1' is not typed")
                                            .arg(op.token().lexeme()));
            new_contract.spec().set(variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_CONTRACT);
        }
        else
            new_contract.spec().set(variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_TYPE);

        stack().push(new_contract);

        notifyDeclared(new_contract);

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeDeclaration(const ast::Node & op)
    {
        if (stack().find(op.token().lexeme(),interpret::BoundScope::Local) != UNDEFINED_NAME_INDEX) {
            stack().push(variable::error_variable());
            throw AnalyzeException( "ScriptSemantics_abstract::executeDeclaration", 
                                    op.token().location().makeLocation(inter().files()), 
                                    inout::fmt("The name '%1' has already been declared in the local scope")
                                        .arg(op.token().lexeme()));
        }

        if (ScriptOperationCode(op.operation()) == ScriptOperationCode::AutoDeclaration) {
            stack().accumulator() = {u"", {variable::ValueType::Null}, op.token().location()};
            stack().accumulator().spec().set(SPEC_NAME_INITIAL_VALUE, variable::ValueType::Null);
        }

        variable::Variable      var = createVariable(stack().accumulator(), op.token(), variable::SPEC_ORIGIN_VARIABLE);

        if (_function_definition_mode)
            var.spec().set(variable::SPEC_PARAMETER, true);
        else if (stack().getTopBoundaryMarkAndScope().first == BOUNDARY_MARK_MODULE_FRAME)
            var.spec().set(variable::SPEC_PROPERTY, true);

        stack().push(var);

        notifyDeclared(stack().variable(stack().top()));

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeDeclarationCompletion()
    {
        stack().accumulator() = {};
        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeAssignment(const ast::Node & op)
    {
        bool iteration = false;
        switch (ScriptOperationCode(op.operation()))
        {
        case ScriptOperationCode::AssignmentAddition:
        case ScriptOperationCode::AssignmentSubtraction:
        case ScriptOperationCode::AssignmentMultiplication:
        case ScriptOperationCode::AssignmentDivision:
        case ScriptOperationCode::AssignmentModulo:
            stack().pushRef(stack().variable(stack().top()), op.token().location());
            iteration = true;
            break;
        default:
            break;
        }

        if (_setter_in_stack) {
            _setter_in_stack = false;
            if (iteration) {
                stack().pop();
                stack().push(variable::error_variable(op.token().location()));             
                throw AnalyzeException( "ScriptSemantics_abstract::executeAssignment", 
                                        op.token().location().makeLocation(inter().files()), 
                                        inout::fmt("Assignment-iteration is not allowed for the setter"));
            }
            return executeFunctionCall(op);
        }

        inter().addFlowLayer(
                op, 
                [this, iteration](const FlowLayerInfo & flow) 
                {
                    if (flow.error_sign) {
                        stack().pop();
                        return;
                    }

                    if (iteration)
                        try
                        {
                            arithmetic(ScriptOperationCode(flow.code.operation()), flow.code.token().location());
                        }
                        catch(...)
                        {
                            stack().pop();
                            throw;
                        }

                    name_index_t         source_index    = stack().top(0);
                    variable::Variable & source_variable = stack().variable(source_index);
                    name_index_t         target_index    = stack().top(1);
                    variable::Variable & target_variable = stack().variable(target_index);
                    ScriptOperationCode  operation_code  = ScriptOperationCode(flow.code.operation());

                    inter().expr().assignVariable(target_variable, source_variable);
                    stack().pop(); // правая часть присваивания

                    if (operation_code == ScriptOperationCode::Initialize)
                        notifyInitiated(target_variable.origin());
                    else
                        stack().pop(); // ссылка на переменную

                    name_index_t                var_index   = stack().top(0);
                    const variable::Variable &  var         = stack().variable(var_index);

                    notifyNameUsed(var.origin(), var.location());
                });

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeGroupInitialize(const ast::Node & op, std::vector<const inout::Token *> /*ids*/, const ast::Node & /*rvalue_ops*/)
    {
        throw AnalyzeException("ScriptSemantics_abstract::executeGroupInitialize", 
                                op.token().makeLocation(inter().files()), 
                                inout::fmt("Group initialization is not supported"));

        // inter().addFlowLayer(
        //         rvalue_ops, 
        //         [this, ids](const FlowLayerInfo &) 
        //         {
        //             variable::Variable  r_variable;
        //             variable::Value *   rvalue;

        //             {
        //                 variable::Variable & top = stack().variable(stack().top());

        //                 if (!top.origin().value().isArray() && !top.origin().value().isObject())
        //                     r_variable = _engine.convertVariable_overridable(top,variable::ValueType::Array);
        //                 else
        //                     r_variable = top.origin();

        //                 rvalue = &r_variable.value();
        //             }

        //             stack().pop();

        //             for(size_t i=0; i < ids.size(); ++i) {
        //                 const inout::Token * id = ids[i];
        //                 if (id->type() == inout::LexemeType::Id) {
        //                     if (stack().find(id->lexeme(),BoundScope::Local) != UNDEFINED_NAME_INDEX)
        //                         throw AnalyzeException("ScriptSemantics_abstract::executeDeclaration", 
        //                                                 id->makeLocation(inter().files()), 
        //                                                 "Variable '" + inout::toU8(id->lexeme()) + "' already declared in local scope");

        //                     const variable::Value *    rvalue_element;

        //                     if (rvalue->isArray())
        //                         rvalue_element = &(rvalue->getArray()->getValueByIndex(i));
        //                     else
        //                         rvalue_element = &(rvalue->getObject()->getVariableByIndex(i).value());

        //                     _engine.push({ id->lexeme(), rvalue_element->copy(), id->location(), 
        //                                     variable::Object {{{variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_VARIABLE}, }} });
        //                     notifyDeclared(stack().variable(stack().top()));
        //                 }
        //             }
        //         });

        // return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeUnary(const ast::Node & op)
    {
        unary(static_cast<ScriptOperationCode>(op.operation()), op.bound().location());
        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeLogical(const ast::Node & op)
    {
        name_index_t top_name_index = stack().top();

        try
        {
            logical(static_cast<ScriptOperationCode>(op.operation()), op.bound().location());
        }
        catch(...)
        {
            stack().pop(stack().top()+1-top_name_index);
            stack().variable(stack().top()) = variable::error_variable().copyVariable();
            throw;
        }

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeCompare(const ast::Node & op)
    {
        name_index_t top_name_index = stack().top();

        try
        {
            compare(static_cast<ScriptOperationCode>(op.operation()), op.bound().location());
        }
        catch(...)
        {
            stack().pop(stack().top()+1-top_name_index);
            stack().variable(stack().top()) = variable::error_variable().copyVariable();
            throw;
        }

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeArithmetic(const ast::Node & op)
    {
        name_index_t top_name_index = stack().top();

        try
        {
            arithmetic(static_cast<ScriptOperationCode>(op.operation()), op.bound().location());
        }
        catch(...)
        {
            stack().pop(stack().top()+1-top_name_index);
            stack().variable(stack().top()) = variable::error_variable().copyVariable();
            throw;
        }

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeConditional(ScriptOperationCode code, const ast::Node & op_true, const ast::Node * op_false)
    {
        name_index_t       condition_index    = stack().top();
        variable::Variable condition_variable = stack().variable(condition_index).origin();

        try
        {
            bool    condition;

            if (condition_variable.type() == variable::ValueType::Bool) {
                condition = condition_variable.value().getBool();
            }
            else {
                condition_variable = inter().expr().convertVariable(condition_variable, variable::ValueType::Bool);
                condition          = condition_variable.value().getBool();
            }

            stack().pop();

            if (condition)
                inter().addFlowLayer(op_true);
            else if (op_false != nullptr)
                inter().addFlowLayer(*op_false);

            return InterpretState::Flow;
        }
        catch(...)
        {
            if (code == ScriptOperationCode::Ternary)
                stack().pop(stack().top() - condition_index);

            throw;
        }

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeApply(const ast::Node & , const std::vector<inout::Token> & names)
    {
        for(const inout::Token & t : names) {
            name_index_t name_index = stack().find(t.lexeme());
            if (name_index == UNDEFINED_NAME_INDEX)
                throw AnalyzeException( "ScriptSemantics_abstract::executeApply", 
                                        t.location().makeLocation(inter().files()), 
                                        inout::fmt("The name '%1' not found")
                                            .arg(t.lexeme()));

            if (stack().addGlobal(name_index))
                notifyNameUsed(stack().variable(name_index), t.location());
            else
                throw AnalyzeException( "ScriptSemantics_abstract::executeApply", 
                                        t.location().makeLocation(inter().files()), 
                                        inout::fmt("Unable to make the name '%1' global")
                                            .arg(t.lexeme()));
        }

        return InterpretState::Flow;
    }

    // InterpretState ScriptSemantics_abstract::executeAutoDefine(const ast::Node & op)
    // {
    //     variable::VariableSet_t record_structure = {{SPEC_NAME_INITIAL_VALUE, {}}};

    //     appendContractProperties(variable::Variable {u"auto", record_structure, op.token().location(),
    //                             {{{variable::SPEC_ORIGIN, variable::SPEC_ORIGIN_STRUCTURE}}}}, 
    //             stack().accumulator());


    //     return InterpretState::Flow;
    // }

    InterpretState ScriptSemantics_abstract::executeFiberMake(const ast::Node & op)
    {
        inter().addFlowLayer(
                op, 
                [this](const FlowLayerInfo & flow) 
                {
                    if (flow.error_sign) {
                        stack().pop();
                        return;
                    }

                    try {
                        variable::Variable & fiber_variable_ref    = stack().variable(stack().top());
                        variable::Variable & fiber_variable_origin = fiber_variable_ref.origin();

                        if (!fiber_variable_origin.value().isObject())
                            throw AnalyzeException( "ScriptSemantics_abstract::executeFiberOperation", 
                                                    fiber_variable_ref.location().makeLocation(inter().files()), 
                                                    inout::fmt("Only a variable with an object type can act as a fiber"));

                        std::shared_ptr<variable::Object> object = fiber_variable_origin.value().getObject();

                        if (object->flags().isSet(variable::ObjectFlag::Fiber)) {
                            std::string object_name_text = fiber_variable_ref.name().empty()
                                                         ? inout::toU8(fiber_variable_origin.name()) 
                                                         : inout::toU8(fiber_variable_ref.name());

                            if (!object_name_text.empty())
                                object_name_text = " '" + object_name_text + "'";

                            throw AnalyzeException( "ScriptSemantics_abstract::executeFiberOperation", 
                                                    fiber_variable_ref.location().makeLocation(inter().files()), 
                                                    inout::fmt("Object %1 has already been declared as a fiber")
                                                        .arg(object_name_text));
                        }

                        if (!inter().parallelize().addChildFiber(fiber_variable_origin))
                            throw bormental::DrBormental("ScriptSemantics_abstract::executeFiberOperation",
                                                inout::fmt("Unable to create fiber"));

                        object->flags().set(variable::ObjectFlag::Fiber);
                    }
                    catch(...) {
                        stack().pop();
                        throw;
                    }

                    stack().pop();
                });

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeFiberWait(const ast::Node & op)
    {
        inter().addFlowLayer(
                op, 
                [this](const FlowLayerInfo & flow) 
                {
                    if (flow.error_sign) {
                        return;
                    }

                    if (checkInterpretType(InterpretType::Preview))
                        if (!inter().parallelize().wait(prepareFiberVariableOrigin()))
                            throw bormental::DrBormental("ScriptSemantics_abstract::executeFiberWait",
                                                inout::fmt("Unable to wait fiber"));
                });

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeFiberPushPull(const ast::Node & op)
    {
        try {
            switch (ScriptOperationCode(op.operation()))
            {
            case ScriptOperationCode::FiberPush:
                if (checkInterpretType(InterpretType::Preview))
                    if (!inter().parallelize().push(prepareFiberVariableOrigin()))
                        throw bormental::DrBormental("ScriptSemantics_abstract::executeFiberPushPull",
                                                    inout::fmt("Unable to push fiber"));
                break;
            case ScriptOperationCode::FiberPull:
                if (checkInterpretType(InterpretType::Preview))
                    if (!inter().parallelize().pull(prepareFiberVariableOrigin()))
                        throw bormental::DrBormental("ScriptSemantics_abstract::executeFiberPushPull",
                                                    inout::fmt("Unable to pull fiber"));
                break;
            default:
                throw bormental::DrBormental("ScriptSemantics_abstract::executeFiberPushPull",
                                            inout::fmt("Unexpected operation"));
            }
        }
        catch(...) {
            stack().pop();
            throw;
        }

        stack().pop();

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeFiberFlow(const ast::Node & op)
    {
        _fiber_flow_mode = FiberFlowMode::InDemand;

        inter().addFlowLayer(
                op, 
                [this](const FlowLayerInfo & flow) 
                {
                    if (_fiber_flow_mode != FiberFlowMode::Launched)
                        throw AnalyzeException( "ScriptSemantics_abstract::executeFiberOperation", 
                                                flow.code.token().location().makeLocation(inter().files()), 
                                                inout::fmt("Unable to launch remote procedure"));

                    _fiber_flow_mode = FiberFlowMode::None;
                });

        return InterpretState::Flow;
    }

    InterpretState ScriptSemantics_abstract::executeCheckState(const ast::Node & )
    {
        return InterpretState::Flow;
    }

}
